package bebetes.test;

import junit.framework.TestCase;
import bebetes.Bebete;
import bebetes.BebeteEmergente;
import bebetes.ChampDeBebetes;

/**
 * Classe de tests pour la classe {@link ChampDeBebetes}. Test de la méthode
 * {@link visu.PerceptionAble#getChosesVues()}.
 * <p>
 * Cette classe teste un champ qui contient deux bêbètes Emergente, mais leur
 * statut n'a pas vraiment d'importance car ils ne se basent que sur la position
 * des bêbètes. Chaque test consiste à disposer les deux bêbètes à deux
 * positions précises, et de voir si l'une est capable de voir l'autre, et
 * réciproquement.
 * <p>
 * Si la Bébête A peut voir la bébête B, alors la liste retrounée par la méthode
 * getChosesVues() de la classe visu.PositionAble contient un seul élément, qui
 * est B. Pour simplifier les tests, on se contente donc de comparer la taille
 * de la liste retournée avec les valeurs 0 (aucune bébête n'est visible) ou 1,
 * l'autre bébête du champ est visible.
 * <p>
 * La convention de cette partie du code au niveau des commentaires est la
 * suivante : {@code //} délimite une ligne de code commentée, {@code /*.../}
 * une explication du code.
 * 
 * @author gnpt
 */
public class ChampDeBebetesTest1 extends TestCase {
	private ChampDeBebetes champDeBebetes = null;
	private Bebete A = null;
	private Bebete B = null;
	private static float pi = (float) Math.PI;

	/**
	 * Méthode d'initialisation de chaque test. On crée un champ vide, et on y
	 * place deux BebeteEmergente, qui sont référencé par cette classe, afin de
	 * simplifier les manipulations.
	 */
	@Override
	protected void setUp() throws Exception {
		super.setUp();
		this.champDeBebetes = new ChampDeBebetes(50, 50, 0);
		this.A = new BebeteEmergente(this.champDeBebetes, 0, 0, 0, 0,
				java.awt.Color.green);
		this.B = new BebeteEmergente(this.champDeBebetes, 0, 0, 0, 0,
				java.awt.Color.red);
		java.util.List<Bebete> list = new java.util.ArrayList<Bebete>();
		list.add(this.A);
		list.add(this.B);
		this.champDeBebetes.setDessinables(list);
	}

	/**
	 * Méthode de destruction après chaque test, chacun des champs de l'instance
	 * est mis à null afin de demander au "Ramasse-miètes" de faire son travail.
	 */
	@Override
	protected void tearDown() throws Exception {
		super.tearDown();
		this.champDeBebetes = null;
		this.A = null;
		this.B = null;
	}

	/**
	 * Deux bébêtes, dos à dos, proches.
	 * <p>
	 * Les bébêtes ne doivent pas pouvoir se voir vu qu'elles se tournent le
	 * dos.
	 */
	public void testBebetesDosADosProches() {

		/* A en (5,10) qui regarde à l'ouest. */
		this.A.setX(5);
		this.A.setY(10);
		this.A.setDirectionCourante(-pi);

		/* B en (15,10) qui regarde à l'est. */
		this.B.setX(15);
		this.B.setY(10);
		this.B.setDirectionCourante(0f);

		/*
		 * Comme dit dans le commentaire de classe, la taille de la collection
		 * suffit à déterminer si elles se voient ou non.
		 */
		assertTrue("A ne voit pas B", this.A.getChosesVues().size() == 0);
		assertTrue("B ne voit pas A", this.B.getChosesVues().size() == 0);
	}

	/**
	 * Deux bébêtes, dos à dos, lointaines
	 * <p>
	 * A priori, si le test précédent ne passe pas, il n'y a aucune raison que
	 * celui-ci passe, dans la mesure où elles se sont éloignées l'une de
	 * l'autre.
	 */
	public void testBebetesDosADosLointaines() {

		/* A toujours en (5,10) qui regarde encore à l'ouest. */
		this.A.setX(5);
		this.A.setY(10);
		this.A.setDirectionCourante(-pi);

		/* B plus éloignée selon les x en (45,10) qui regarde à... l'est ! */
		this.B.setX(45);
		this.B.setY(10);
		this.B.setDirectionCourante(0f);

		assertTrue("A ne voit pas B", this.A.getChosesVues().size() == 0);
		assertTrue("B ne voit pas A", this.B.getChosesVues().size() == 0);
	}

	/**
	 * Deux bébêtes, face à face, proches.
	 * <p>
	 * Cette fois les bébêtes se font face, elles doivent donc se voir.
	 */
	public void testBebetesFaceAFaceProches() {

		/* A en (5,10) dirigée vers l'est. */
		this.A.setX(5);
		this.A.setY(10);
		this.A.setDirectionCourante(0f);

		/* B en (15,10) dirigée vers l'ouest. */
		this.B.setX(15);
		this.B.setY(10);
		this.B.setDirectionCourante(-pi);

		assertTrue("A voit B", this.A.getChosesVues().size() == 1);
		assertTrue("B voit A", this.B.getChosesVues().size() == 1);
	}

	/**
	 * Deux bébêtes, face à face, lointaines.
	 * <p>
	 * Même que précédemment, mais avec plus de distance. Donc elles ne se
	 * voient plus.
	 */
	public void testBebetesFaceAFaceLointaines() {

		/* A en (5,10) dirigée vers l'est. */
		this.A.setX(5);
		this.A.setY(10);
		this.A.setDirectionCourante(0f);

		/* B en (45,10) dirigée vers l'ouest. */
		this.B.setX(45);
		this.B.setY(10);
		this.B.setDirectionCourante(-pi);

		assertTrue("A ne voit pas B", this.A.getChosesVues().size() == 0);
		assertTrue("B ne voit pas A", this.B.getChosesVues().size() == 0);
	}

	/**
	 * Deux bébêtes, l'une tournant le dos à l'autre, proches. Elles sont donc
	 * dirigées suivant la même direction toutes les deux.
	 */
	public void testBebetesFaceADosProches() {

		/* A en (5,10) dirigée vers l'est. */
		this.A.setX(5);
		this.A.setY(10);
		this.A.setDirectionCourante(0f);

		/* B en (15,10) dirigée vers l'est. */
		this.B.setX(15);
		this.B.setY(10);
		this.B.setDirectionCourante(0f);

		assertTrue("A voit B", this.A.getChosesVues().size() == 1);
		assertTrue("B ne voit pas A", this.B.getChosesVues().size() == 0);
	}

	/**
	 * Deux bébêtes, l'une tournant le dos à l'autre, lointaines.
	 * <p>
	 * Même test que précédemment, mais B est plus loin.
	 */
	public void testBebetesFaceADosLointaines() {

		/* A en (5,10) dirigée vers l'est. */
		this.A.setX(5);
		this.A.setY(10);
		this.A.setDirectionCourante(0f);

		/* B en (45,10) dirigée vers l'est. */
		this.B.setX(45);
		this.B.setY(10);
		this.B.setDirectionCourante(0f);

		assertTrue("A ne voit pas B", this.A.getChosesVues().size() == 0);
		assertTrue("B ne voit pas A", this.B.getChosesVues().size() == 0);
	}

	/**
	 * Deux bébêtes, sur la même case.
	 * <p>
	 * La correction de code effectuée sur la classe utilitaire
	 * util.DistancesEtDirections, sur la méthode directionDepuisUnPoint(). En
	 * effet, entre deux points confondus passe une infinité de droite, donc
	 * l'angle entre cette droite et l'axe de référence n'est pas défini d'un
	 * point de vue mathématique. Donc deux bébêtes à la même position, bien
	 * qu'elles se marchent dessus, ne se voient pas. C'est un choix
	 * d'implémentation.
	 */
	public void testBebetesMemePosition() {
		/**
		 * Un petit générateur aléatoire, vu que la direction courante de la
		 * bébête n'a aucune influence sur le résultat du calcul effectué par
		 * getChosesVues().
		 * <p>
		 * Il n'est pas conseillé d'utiliser des valeurs aléatoires pour les
		 * tests, dans la mesure où les conditions du tests deviennent
		 * variables, et cela peut rendre le test non déterministe. C'est pour
		 * celà que les méthodes setDirectionCourante() sont commentées.
		 */
		// java.util.Random rand = new java.util.Random();
		/* A en (25,25). */
		this.A.setX(25);
		this.A.setY(25);
		// A.setDirectionCourante(rand.nextFloat() * 2f * pi);

		/* B en (25,25). */
		this.B.setX(25);
		this.B.setY(25);
		// B.setDirectionCourante(rand.nextFloat() * 2f * pi);

		assertTrue("A ne voit pas B", this.A.getChosesVues().size() == 0);
		assertTrue("B ne voit pas A", this.B.getChosesVues().size() == 0);
	}

	/**
	 * Deux bébêtes, tests aux bornes du ChampDeVue.
	 * <p>
	 * Ce test est un peu plus complexe, dans la mesure où dans cette seule
	 * méthode, plusieurs variante du même test sont simulées.
	 * <p>
	 * En premier, A regarde plein est, on place d'abord B à la limite de
	 * visibilité de A dans l'angle sud-est du champs de vue, puis de même dans
	 * l'angle nord-est.
	 * <p>
	 * Ensuite on fait varier l'abscisse de B, puis son ordonnée, afin de
	 * montrer que B sort du champs de vue de A au pixel près.
	 */
	public void testBebetesChampDeVue() {

		/* A en (25,25), dirigée plein est. */
		this.A.setX(25);
		this.A.setY(25);
		this.A.setDirectionCourante(0f);

		/* B placé à la limite du champ de vue de A, secteur sud-est. */
		this.B.setX(25 + Math.round(this.A.getLongueurDeVue()
				* (float) Math.cos(this.A.getChampDeVue() / 2f)));
		this.B.setY(25 + Math.round(this.A.getLongueurDeVue()
				* (float) Math.sin(this.A.getChampDeVue() / 2f)));

		/* A doit voir B. */
		assertTrue("A voit B", this.A.getChosesVues().size() == 1);

		/* B placé à la limite du champ de vue de A, secteur nord-est. */
		this.B.setY(25 - Math.round(this.B.getLongueurDeVue()
				* (float) Math.sin(this.B.getChampDeVue() / 2f)));

		/* A doit toujours voir B. */
		assertTrue("A voit B", this.A.getChosesVues().size() == 1);

		/* B s'éloigne d'un pixel selon la direction de A. */
		this.B.setX(this.B.getX() + 1);

		/* B s'est trop éloigné de A. */
		assertTrue("A ne voit pas B", this.A.getChosesVues().size() == 0);

		/* B revient à la position précédente. */
		this.B.setX(this.B.getX() - 1);

		/* idem que l'avant-dernier, on est revenu au point initial. */
		assertTrue("A voit B", this.A.getChosesVues().size() == 1);

		/* B s'éloigne d'un pixel perpendiculairement à la direction de A. */
		this.B.setY(this.B.getY() - 1);

		/* B n'est plus visible par A. */
		assertTrue("A ne voit pas B", this.A.getChosesVues().size() == 0);
	}
}
